package com.lushu.data {
import com.lushu.data.interfaces.IWritableLayer;
import com.sfdk.data.LayerBitmapKeyPair;
import com.sfdk.graphics.engine.api.ILayerInformation;
import com.sfdk.graphics.engine.api.IMapObject;
import com.sfdk.graphics.engine.api.ITileset;
import com.sfdk.graphics.engine.api.IWalkableLayerInformation;
import com.sfdk.graphics.engine.core.enums.LayerType;
import com.sfdk.graphics.engine.data.TileInfo;
import com.sfdk.utils.Base64;

import flash.geom.Point;
import flash.utils.ByteArray;

public class EditorLayerData implements ILayerInformation, IWritableLayer, IWalkableLayerInformation {
    private var xmlData_:XML;
    private var tileData_:Vector.<Vector.<TileInfo>>;
    private var currentMap_:Zone;

    private var isWalkable_:Boolean = false;

    private var layerWidth_:int;
    private var layerHeight_:int;

    private var tilesetNameList_:Vector.<String>;
    private var tilesetLengthList_:Vector.<int>;
    private var cumulTilesetLengthList_:Vector.<int>;
    private var tilesetLengthListSize_:int;

    private var tileInfo_:TileInfo;

    private var tempTiles_:Vector.<TileSelection>;
    private var _layerType : LayerType;

    public function EditorLayerData(xmlData:XML, map:Zone, layerType : LayerType) {
        if(layerType == LayerType.WALKABLE_TYPE )
            isWalkable_ = true;
        else
            isWalkable_ = false;
        xmlData_ = xmlData;
        currentMap_ = map;
        _layerType = layerType;
        tileData_ = new Vector.<Vector.<TileInfo>>();
        parseData();
        tileInfo_ = new TileInfo(null, 0, 0);
        tempTiles_ = new Vector.<TileSelection>();
    }

    public function changeSize(width:int, height:int):void {
        var x:int;
        var y:int;
        layerWidth_ = width;
        layerHeight_ = height;
        var newTileData:Vector.<Vector.<TileInfo>> = new Vector.<Vector.<TileInfo>>();
        for (y = 0; y < layerHeight_; y++) {
            newTileData[y] = new Vector.<TileInfo>();
            for (x = 0; x < layerWidth_; x++) {
                if (y < tileData_.length && x < tileData_[y].length)
                    newTileData[y][x] = tileData_[y][x];
                else
                    newTileData[y][x] = new TileInfo(null, -1, -1);
            }
        }
        tileData_ = newTileData;
    }

    private function parseData():void {
        var srcTiles:XMLList = xmlData_.data.tile;
        var encoding:String = xmlData_.data.@encoding;
        layerWidth_ = int(Number(xmlData_.@width));
        layerHeight_ = int(Number(xmlData_.@height));
        var x:int;
        var y:int;

        if (xmlData_.data[0] == null) {
            trace("layer <data> tag enclosed no data. (empty data tag)");
        }
        else {
            var enc:String = xmlData_.data[0];
            var dec:ByteArray = Base64.decode(enc);
            var isa:ByteArray;
            var comp:String = xmlData_.data.@compression

            dec.uncompress();

            isa = dec;

            var mapHeight:int = currentMap_.heightInTile;
            var mapWidth:int = currentMap_.widthInTile;
            for (y = 0; y < mapHeight; y++) {
                tileData_[y] = new Vector.<TileInfo>();
                for (x = 0; x < mapWidth; x++) {
                    var tilesetID:int = 0;
                    var tileX:int = 0;
                    var tileY:int = 0;

                    var k:int = (x + y * mapWidth) * 6;
                    tilesetID |= (isa[k]);
                    tilesetID |= (isa[k + 1] << 8 );

                    tileX |= (isa[k + 2]);
                    tileX |= (isa[k + 3] << 8 );

                    tileY |= (isa[k + 4]);
                    tileY |= (isa[k + 5] << 8 );


                    if (tilesetID == 0 || tileX < 0 || tileY < 0) {
                        tileData_[y][x] = new TileInfo(null, -1, -1);
                    }
                    else {
                        if (tilesetID == 1)
                            tileData_[y][x] = new TileInfo(currentMap_.getWalkableTileset(), tileX, tileY);
                        else {
                            var tileset:ITileset = currentMap_.getTileset(tilesetID);
                            tileData_[y][x] = new TileInfo(tileset, tileX, tileY);

                        }

                    }
                }
            }
        }
    }

    public function getInfo(x:int, y:int):TileInfo {
        return tileData_[y][x];
    }

    public function setTile(tiles:Vector.<TileSelection>, point:Point):Vector.<Point> {
        var modifiedTiles:Vector.<Point> = new Vector.<Point>();
        clearHover(modifiedTiles);
        var currTileselect:TileSelection;
        var tilesetIndex:int;

        var currentInfo:TileInfo;

        for (var i:int = 0; i < tiles.length; i++) {
            currTileselect = tiles[i];
            if (point.y + currTileselect.point.y >= 0 && point.y + currTileselect.point.y < tileData_.length && point.x + currTileselect.point.x >= 0 && point.x + currTileselect.point.x < tileData_[0].length) {
                currentInfo = tileData_[point.y + currTileselect.point.y][point.x + currTileselect.point.x];
                currentInfo.setData(currTileselect.tileset, currTileselect.tileX, currTileselect.tileY);
                modifiedTiles.push(new Point(point.x + currTileselect.point.x, point.y + currTileselect.point.y));
            }

        }
        return modifiedTiles;
    }

    public function setTempTile(tiles:Vector.<TileSelection>, point:Point):Vector.<Point> {
        var modifiedTiles:Vector.<Point> = new Vector.<Point>();
        clearHover(modifiedTiles);

        // On stocke les infos précédentes
        var currTileselect:TileSelection;
        var newTileselect:TileSelection;
        var tilesetIndex:int;

        var currentTileInfo:TileInfo;

        for (var i:int = 0; i < tiles.length; i++) {
            currTileselect = tiles[i];
            if (currTileselect != null && point.y + currTileselect.point.y >= 0 && point.y + currTileselect.point.y < tileData_.length && point.x + currTileselect.point.x >= 0 && point.x + currTileselect.point.x < tileData_[0].length) {
                currentTileInfo = tileData_[point.y + currTileselect.point.y][point.x + currTileselect.point.x];
                newTileselect = new TileSelection(currentTileInfo.tileset, currentTileInfo.tileX, currentTileInfo.tileY);
                newTileselect.point = new Point(point.x + currTileselect.point.x, point.y + currTileselect.point.y);
                tempTiles_.push(newTileselect);

                currentTileInfo.setData(currTileselect.tileset, currTileselect.tileX, currTileselect.tileY);
                modifiedTiles.push(new Point(point.x + currTileselect.point.x, point.y + currTileselect.point.y));
            }
        }

        return modifiedTiles;
    }

    public function fillTile(tiles:Vector.<TileSelection>, point:Point, previousPoint:Point):Vector.<Point> {
        var currentTileInfo:TileInfo;
        var convexTileInfo:Point;
        var modifiedTiles:Vector.<Point> = new Vector.<Point>();
        currentTileInfo = tileData_[point.y][point.x];

        clearHover(modifiedTiles);

        var currTileselect:TileSelection;
        currTileselect = tiles[0];

        var convexedData:Vector.<Point> = convexifie(tileData_, point);

        for (var j:int = 0; j < convexedData.length; j++) {
            convexTileInfo = convexedData[j];
            currentTileInfo = tileData_[convexTileInfo.y][convexTileInfo.x];
            currentTileInfo.setData(currTileselect.tileset, currTileselect.tileX, currTileselect.tileY);
            modifiedTiles.push(new Point(convexTileInfo.x, convexTileInfo.y));
        }
        return modifiedTiles;
    }

    public function tempFillTile(tiles:Vector.<TileSelection>, point:Point, previousPoint:Point):Vector.<Point> {
        var currentTileInfo:TileInfo;
        var convexTileInfo:Point;
        var previousTileInfo:TileInfo;
        var modifiedTiles:Vector.<Point> = new Vector.<Point>();

        if (point.y < tileData_.length && point.y < tileData_[0].length) {
            currentTileInfo = tileData_[point.y][point.x];
            previousTileInfo = tileData_[previousPoint.y][previousPoint.x];
            var currTileselect:TileSelection;
            currTileselect = tiles[0];

            if (!currentTileInfo.equals(previousTileInfo) && (currentTileInfo.tileset != currTileselect.tileset || currentTileInfo.tileX != currTileselect.tileX || currentTileInfo.tileY != currTileselect.tileY)) {
                clearHover(modifiedTiles);

                var convexedData:Vector.<Point> = convexifie(tileData_, point);
                var newTileselect:TileSelection;

                for (var j:int = 0; j < convexedData.length; j++) {
                    convexTileInfo = convexedData[j];
                    currentTileInfo = tileData_[convexTileInfo.y][convexTileInfo.x];
                    newTileselect = new TileSelection(currentTileInfo.tileset, currentTileInfo.tileX, currentTileInfo.tileY);
                    newTileselect.point = new Point(convexTileInfo.x, convexTileInfo.y);

                    tempTiles_.push(newTileselect);
                    currentTileInfo.setData(currTileselect.tileset, currTileselect.tileX, currTileselect.tileY);
                    modifiedTiles.push(new Point(convexTileInfo.x, convexTileInfo.y));
                }
            }
        }
        return modifiedTiles;
    }

    private function convexifie(map:Vector.<Vector.<TileInfo>>, initPoint:Point):Vector.<Point> {
        var taille1:int = map.length
        var taille2:int = map[0].length;
        var i:int, j:int;
        var foret:Vector.<Point> = new Vector.<Point>();
        var foret2:Vector.<Vector.<Boolean>> = new Vector.<Vector.<Boolean>>();

        for (j = 0; j < taille1; j++) {
            foret2[j] = new Vector.<Boolean>();
            for (i = 0; i < taille2; i++) {
                foret2[j][i] = false;
            }
        }
        function remplissage(departx:int, departy:int, map:Vector.<Vector.<TileInfo>>, foret:Vector.<Point>, foret2:Vector.<Vector.<Boolean>>):void {
            var pile:Array = new Array();
            var west:Object;
            var est:Object;
            var tilevaleur:TileInfo = map[departy][departx];
            pile.push(new Point(departx, departy));

            while (pile.length != 0) {
                var tmp:Point = pile.pop() as Point;
                if (!foret2[tmp.y][tmp.x]) {
                    west = new Point(tmp.x, tmp.y);

                    est = new Point(tmp.x, tmp.y);

                    while (west.x > 0 && tilevaleur.equals(map[west.y][west.x - 1]) && !foret2[west.y][west.x])
                        west.x -= 1

                    while (est.x < (map[0].length - 1) && !foret2[est.y][est.x] && tilevaleur.equals(map[est.y][est.x + 1]))
                        est.x += 1

                    for (var i:int = west.x; i <= est.x; i++) {
                        foret.push(new Point(i, tmp.y));
                        foret2[tmp.y][i] = true;
                        if (tmp.y > 0 && tilevaleur.equals(map[tmp.y - 1][i]))
                            pile.push(new Point(i, tmp.y - 1));
                        if (tmp.y < (map.length - 1) && tilevaleur.equals(map[tmp.y + 1][i]))
                            pile.push(new Point(i, tmp.y + 1));
                    }
                }
            }
            return;
        }

        remplissage(initPoint.x, initPoint.y, map, foret, foret2);

        return foret;
    }

    public function clearHover(modifiedTiles:Vector.<Point>):void {
        var currTileselect:TileSelection;
        var currentInfo:TileInfo;

        for (var i:int = 0; i < tempTiles_.length; i++) {
            currTileselect = tempTiles_[i];
            currentInfo = tileData_[currTileselect.point.y][currTileselect.point.x];
            currentInfo.setData(currTileselect.tileset, currTileselect.tileX, currTileselect.tileY);
            modifiedTiles.push(new Point(currTileselect.point.x, currTileselect.point.y));
        }

        tempTiles_.splice(0, tempTiles_.length);
    }

    public function getData():Vector.<Vector.<TileInfo>> {
        return tileData_;
    }

    public function getTilesetName():Vector.<String> {
        return tilesetNameList_;
    }

    public function setTilesetName(list:Vector.<String>):void {
        tilesetNameList_ = list;
    }

    public function setTilesetLength(list:Vector.<int>):void {
        tilesetLengthList_ = list;
        tilesetLengthListSize_ = list.length;

        cumulTilesetLengthList_ = new Vector.<int>(tilesetLengthListSize_);
        var cumul:int = 0;
        for (var i:int = 0; i < tilesetLengthListSize_; i++) {
            cumulTilesetLengthList_.push(cumul);
            cumul += tilesetLengthList_[i];
        }
    }

    public function isWalkableLayer():Boolean {
        return isWalkable_;
    }

    public function getWidth():int {
        return layerWidth_;
    }

    public function getHeight():int {
        return layerHeight_;
    }


    private var _isVisible : Boolean = true;
    public function set isVisible(value:Boolean):void
    {
        _isVisible = value;
    }

    public function get isVisible():Boolean {
        return _isVisible;
    }

    public function get layerType():LayerType {
        return _layerType;
    }

    public function get associatedMap():IMapObject {
        return currentMap_;
    }

    public function get layerData():XML {
        return null;
    }

    public function get tileData():Vector.<Vector.<TileInfo>> {
        return tileData_;
    }

    public function shift(xDir:int, yDir:int):void {
    }

    public function append(xDir:int, yDir:int):void {
    }


    public function isFilled(posX:int, posY:int, width:int, height:int):Boolean {
        return false;
    }

    public function addModule(moduleElement:LayerBitmapKeyPair):void {
    }


    public function isWalkable(x:int, y:int):Boolean {
        return false;
    }
}
}